#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#   Changes
#   20120128 migrate to new core4 with 'process_status' and 'status_type'

__all__ = ['PyMetaInfo', ]
__doc__ = '''Generate meta description for python module. Based on lib/pydoc.py'''

import types
import pkgutil
import inspect
from pydoc import locate, isdata, visiblename

from pyflowctrl import core4 as core 

INPUT_STREAM_SCHEMA = {
    "module": {
        "type": "string",
        "title": "Module name",
        "required": "true",
    }
}

OUTPUT_STREAM_SCHEMA = {
    "name": {
        "type": "string",
        "title": "Module or class name",
        "required": "true", 
    },
    "description": {
        "type": "string",
        "title": "Module or class description",
    },
    "object_type": {
        "type": "string",
        "title": "Module or class type",
        "required": "true",
    },
    "based_on": {
        "type": "array",
        "title": "list of bassed-on classes",
        "items": {
            "type": "string",
            "title": "Based-on class",
        }
    },
    "classes": {
        "type": "array",
        "title": "List of classes in module",
        "items": {
            "type": "object",
            "title": "Class of module",
            "properties:": {
                "name": {
                    "type": "string",
                    "title": "name of class",
                    "required": "true",
                },
                "path": {
                    "type": "string",
                    "title": "path for import class",
                    "required": "true",
                },
                "based_on": {
                    "type": "array",
                    "title": "Based-on classes",
                    "required": "true",
                    "items": {
                        "type": "string",
                        "title": "Based-on class",
                        "required": "false",
                    }
                }
            }
        }
    },
    "packages": {
        "type": "array",
        "title": "list of module packages",
        "items": {
            "type": "string",
            "title": "Package",
        }
    },
    "submodules": {
        "type": "array",
        "title": "list of submodules",
        "items": {
            "type": "string",
            "title": "Submodule",
        }
    },
    "funcs": {
        "type": "array",
        "title": "list of functions",
        "items": {
            "type": "string",
            "title": "function name",
        }
    },
    "data": {   # TODO define it
        "type": "any",
        "title": "list of parameters",
    },
}


class PyMetaInfo(core.Process):
    ''' Extact metadata information about module/class '''
    
    __PROCESS__ = {
                    'name': 'PyMetaInfo',
                    'description': 'Extact metadata information about module/class. Customized for pyflowctrl.',
                    'author': 'Andrey Usov <http://devel.ownport.net>',
                    'url': 'http://pyflowctrl.googlecode.com/hg/pyflowctrl/core4/processes/pymetainfo2.py',
                    'io': { 
                            'input': {'type': core.stream_type.input, 'schema': INPUT_STREAM_SCHEMA,}, 
                            'output': {'type': core.stream_type.output, 'schema': OUTPUT_STREAM_SCHEMA,}, 
                     },
                }

    def get_elements(self, obj, element_type):
        ''' get list of object's elements by type'''
        try:
            all = obj.__all__
        except AttributeError:
            all = None
        elements = []
        for key, value in inspect.getmembers(obj, element_type):
            if visiblename(key, all, value):
                elements.append((key, value))
        return elements

    def get_classes(self, obj):
        ''' return list of classes '''
        classes = []
        for c in self.get_elements(obj, inspect.isclass):
            name = c[0]
            path = '.'.join((c[1].__module__,c[1].__name__))
            based_on = []
            for c in c[1].__bases__:
                based_on.append('.'.join((c.__module__, c.__name__)))
            classes.append((name, path, based_on))
        return classes        

    def get_funcs(self, obj):
        ''' return list of functions, filtered'''
        funcs = []
        for f in self.get_elements(obj, inspect.isroutine):
            if isinstance(f[1], types.MethodType):
                f_name = f[0]
                f_desc = inspect.getdoc(f[1]) or inspect.getcomments(obj)
                funcs.append((f_name, f_desc))
        return funcs
        
    def get_data(self, obj):
        ''' return data for object. filtered'''
        data = []
        for e in self.get_elements(obj, isdata):
            if e[0] not in ['__dict__', '__weakref__']:
                data.append(e)
        return data

    def get_modpkgs(self, obj):
        ''' return list of module's packages '''
        modpkgs = []
        modpkgs_names = set()
        if hasattr(obj, '__path__'):
            for importer, modname, ispkg in pkgutil.iter_modules(obj.__path__):
                modpkgs_names.add(modname)
                if ispkg:
                    modpkgs.append(modname + ' (package)')
                else:
                    modpkgs.append(modname)
            modpkgs.sort()
        return modpkgs

    def remove_empty_meta(self, metadata):
        ''' remove empty data '''
        cleared_meta = {}
        for k in metadata:
            if metadata[k]:
                cleared_meta[k] = metadata[k]
        return cleared_meta

    def get_metadata(self, obj):
        ''' get dictionary of object's metadata '''
        
        return self.remove_empty_meta(metadata)

    def main(self):
        while True:
            try:
                packet = self.io['input'].get()
            except core.EmptyStream:
                yield core.process_status.waiting
                continue
            
            obj = locate(packet.module)
            # remove input fields
            del packet.module
            
            if obj == None:
                yield core.process_status.processing
                continue
            
            packet.name = obj.__name__
            packet.object_type = type(obj)
            packet.description = inspect.getdoc(obj) or inspect.getcomments(obj)
            packet.classes = self.get_classes(obj)
            packet.funcs = self.get_funcs(obj)
            packet.data = self.get_data(obj)
            packet.module_packages = self.get_modpkgs(obj)
            packet.submodules = self.get_elements(obj, inspect.ismodule)

            packet.based_on = []
            if hasattr(obj, '__bases__'):
                for c in list(obj.__bases__):
                    packet.based_on.append('.'.join((c.__module__, c.__name__)))
                
            self.io['output'].put(packet)
            yield core.process_status.processing

    
    
    
